/*---------------------------------------------*
 |  VisualK-OS FBA: Serial COMMS Manager class |
 |  Author: Kevin Slaughter                    |
 | Version: 3.0                                |
 *---------------------------------------------*/

/* Revision History
    v3.0: Initial standardization, after many untracked changes as development progressed.
*/

#ifndef SERIALMANAGER_H
#define SERIALMANAGER_H
  /* Includes */
  #include "CmdConstants.h"
  #include "LightManager.h"
  
  class SerialManager {
    private:
      String _inp = "";           // Command buffer
      boolean _connected;         // Serial connection status
      boolean _handshook;         // ID/sync indication
      boolean _cmdReady;          // Command has been received
      LightManager * _lman;       /* Reference to main LightManager instance.
                                     The FBA boards are used as giant status
                                     indicator LEDs during setup; The Arduino
                                     driving the FBA boards is largely covered,
                                     thus rendering the onboard LED moot...
                                     That and it looks cool. */
      unsigned int _serialAssert; // Connection assurance tick counter
    
    public:
      // Constructor
      SerialManager() {
        _inp = "";
        _connected = false;
        _handshook = false;
        _cmdReady = false;
        _serialAssert = 0;
      }
      
      
      /* ### MACROS */
      void setLM(LightManager* LM) {_lman = LM;}  // Gives this class access to the lighting subsystem
      void appendBuffer(char c) {_inp += c;}       // Append char to cmd buffer
      void clearBuffer() {_inp = "";}              // Clear cmd buffer (externally).
      String getBuffer() {return _inp;}            // Retrieve the cmd buffer contents.
      boolean isConnected() {return _connected;}
      
      
      /* ### METHODS */
      /*-------------------------------------------*
       | Func: assert()                            |
       | Desc: Ensures serial link is and/or stays |
       |       connected, reconnecting if need be. |
       *-------------------------------------------*/
      void assert() {               // Assert connection every ~1sec
        if(_serialAssert>=1000) {
          if(_connected) {_serialAssert = 0;}
          else {init();}      // Retry, if lost
        }
        else {_serialAssert++;}
      }
      
      
      /*-----------------------------------*
       | Func: handOff()                   |
       | Desc: Handle incoming serial data |
       |       within the class' scope.    |
       *-----------------------------------*/
      void handOff() {
        if (Serial.available()) {
          char inC = (char)Serial.read();
          if(inC!='\n') {appendBuffer(inC);}
          else {
            handleCommand(getBuffer()+'\n');
            clearBuffer();
          }
        }
      }
       
       
      /*-------------------------------*
       | Func: init():                 |
       | Desc: Initiate/assert serial  |
       |       link to control center. |
       *-------------------------------*/
      void init() {
        _handshook = false; _connected = false;  // Reset
        boolean bResponse = false; String sHSBuff = ""; char inHS = ' ';
        
        /* Establish link... Loop and block until successful. Nothing
           will work without directive input of some kind (this is a passive system).*/
        while(!_connected) { 
          Serial.begin(COMMS_BAUD_RATE); delay(250);
          _connected = (Serial);
          if(!_connected) {_lman->flashLED(1, 500, VKC_RED);} // Something went fucky
          else {_lman->flashLED(3, 250, VKC_GREEN);}          // Success!
        }
        
        
        // *** ID to control center
        while(!bResponse) {  // Block for response
          _lman->setAllLamps(LS_ON, VKC_YELLOW);   // Indicate that we're waiting
          Serial.println(ID_STR); Serial.flush();
          
          // Wait and check for response (~2.5sec)
          for(int iW=0; iW<10; iW++) {
            delay(250);
            if(Serial.available()) {iW=10; bResponse=true;}  // Yay!
            else {bResponse = false;}  // No response... Wait and retry
          }
          
          // Determine if we need to try again
          if(!bResponse) {_lman->flashLED(LED_SOS, GENERAL_DELAY, VKC_RED);}
        }
        
        
        // *** Wait for and process handshake response
        _lman->setAllLamps(LS_ON, VKC_BLUE);  // Almost there...
        while(_handshook) {
          if(Serial.available()) {
            inHS = (char)Serial.read();
            if(inHS=='\n') {  // Last incoming character was '\n', we're good.
              if(sHSBuff.toInt() == CMD_OK) {_handshook=true;}
              else {sHSBuff="";}  // Didn't get CMD_OK; Clear buffer and try again.
            }
            else {sHSBuff+=inHS; delay(25);} // Add to buffer, wait for '\n'
          }
        }
        
        
        // *** Ready to go!
        _inp = ""; Serial.println(CMD_OK);
        Serial.flush(); delay(5); // Prep for commands
        _lman->flashLED(LED_OK,0,VKC_GREEN);
      }
      
      
      /*-----------------------------------------------------*
       | Func: handleCommand(strCmd)                         |
       | Desc: Execute commands.                             |
       | *** strCmd must start with CMD_PACKETHEADER or the  |
       |     function will exit immediately.                 |
       *-----------------------------------------------------*/
      void handleCommand(String strCmd) {
        /* DEBUG
        int iLS;
        strCmd = strCmd.substring(0,(strCmd.length()-1));  // Remove \n
        Serial.print("[DEBUG] Echo: \""); Serial.print(strCmd); Serial.println("\"");
        if(strCmd[0] == '!') {iLS = LS_OFF; strCmd = strCmd.substring(1);}
        else iLS = LS_ON;
        
        // Dimmer test
        if(strCmd=="D0") {setLampDimmer(0,126);}
        if(strCmd=="D1") {setLampDimmer(1,126);}
        if(strCmd=="D2") {setLampDimmer(2,126);}
        if(strCmd=="D3") {setLampDimmer(3,126);}
        if(strCmd=="D4") {setLampDimmer(4,126);}
        if(strCmd=="D5") {setLampDimmer(5,126);}
        if(strCmd=="D6") {setLampDimmer(6,126);}
        if(strCmd=="D7") {setLampDimmer(7,126);}
        
        // setAllLamps test
        if(strCmd=="ALL!") {setAll(iLS, VKC_NONE);}
        if(strCmd=="ALL0") {setAllLamps(0, iLS, VKC_NONE);}
        if(strCmd=="ALL1") {setAllLamps(1, iLS, VKC_NONE);}
        
        // Full-board color test
        if(strCmd=="R") {setAll(iLS, VKC_RED);}
        if(strCmd=="G") {setAll(iLS, VKC_GREEN);}
        if(strCmd=="B") {setAll(iLS, VKC_BLUE);}
        
        // MassCfg test
        if(strCmd=="MC0") {setMassCfg(0,"1-3-5-8");}
        if(strCmd=="MC1") {setMassCfg(1,"1-8-3-5");}
        
        // RGB lamps
        if(strCmd=="L0") {setLS(0,iLS);}   if(strCmd=="L1") {setLS(1,iLS);}
        if(strCmd=="L2") {setLS(2,iLS);}   if(strCmd=="L3") {setLS(3,iLS);}
        if(strCmd=="L4") {setLS(4,iLS);}   if(strCmd=="L5") {setLS(5,iLS);}
        if(strCmd=="L6") {setLS(6,iLS);}   if(strCmd=="L7") {setLS(7,iLS);}
        
        // White floods
        if(strCmd=="W0") {
          if(iLS==LS_ON) {setLS(0,CMD_WF_ON);}
          if(iLS==LS_OFF) {setLS(0,CMD_WF_OFF);}
        }
        if(strCmd=="W1") {
          if(iLS==LS_ON) {setLS(1,CMD_WF_ON);}
          if(iLS==LS_OFF) {setLS(1,CMD_WF_OFF);}
        }
        return;
        */
        
        
        // --- Non-Debug version of function
        // Tokenize string components
        unsigned int iArg=0; String strTmp=""; boolean bValidCmd=false;
        unsigned int iLight=CMD_NONE; unsigned int iCmd=CMD_NONE; unsigned int iParam=CMD_NONE;
        char cTmp;
        
        // Parse string
        for(unsigned int i=0;i<=strCmd.length();i++) {
          // Process
          cTmp = ' '; cTmp = strCmd.charAt(i);    
          if(cTmp == '^') {
            if(iArg==0) {if(strTmp != PACKETHEADER) {return;}}  // Reject without header
            if(iArg==1) {iCmd = strTmp.toInt();}     // Command index
            if(iArg==2) {iLight = strTmp.toInt();}   // Target light index
            iArg++; strTmp="";
          }
          else if(cTmp == '\n') {  // End of command string
            if(iCmd == CMD_MASS_CFG) {_lman->setMassCfg(iLight, strTmp); return;}
            iParam = strTmp.toInt(); // Avoid need for delimeter after last arg
            bValidCmd = true;
          }
          else {strTmp += cTmp;}  // Copy into buffer
        }
        
        // Process command
        if(bValidCmd) {
          // Offset as needed for future additional FBA banks
          unsigned int o = 0; if(iLight>=5) {o=3;}  // Board #0
          
          switch(iCmd) {
            // CMD_LIGHT_OFF
            //   - iParam: Fade timer (ms), if applicable
            case CMD_LIGHT_OFF:
              _lman->setLS(iLight, LS_OFF);
              break;
            
            // CMD_LIGHT_ON
            //   - iParam: Fade timer (ms), if applicable
            case CMD_LIGHT_ON:
              _lman->setLS(iLight, LS_ON);
              break;
            
            // CMD_SET_VKC: Sets lamp-x's stored color
            //   - iParam: VKC_? color
            case CMD_SET_VKC:
              //HsvColor rgbcon = VKC2HSV(iParam); // Look up color
              //_lman->setColor(iLight, rgbcon);   // Store
              
              // Update color, if already lit
              if(_lman->isOn(iLight)) {_lman->setLS(iLight, LS_ON);}
              break;
            
            // CMD_WF_OFF
            //   - iLight: FBA board
            //   - iParam: Fade timer (ms), if specified
            case CMD_WF_OFF:
              _lman->setLS(iLight, CMD_WF_OFF);
              break;
            
            // CMD_WF_ON: Binary
            //   - iLight: FBA board
            //   - iParam: Fade timer (ms), if specified
            case CMD_WF_ON:
              _lman->setLS(iLight, CMD_WF_ON);
              break;
            
            default:  // Unknown command
              break;
          }
          
          
          //if(iCmd == CMD_COLOR_WALL_ON)   {bColorWall = true;}
          //if(iCmd == CMD_COLOR_WALL_OFF)  {bColorWall = false; ShiftPWM.SetAll(0);}
          
          // Strobe functions
          if(iCmd == CMD_STROBE)  {
            // *** iLight = n/a --- iParam = Strobe program ID
            // Only one strobe program at a time, for now...
            //if(StrobeMan.enabled()) {StrobeMan.stopStrobe();}
            
            // Set and activate new strobe program
            //StrobeMan.setProg((unsigned int)iParam);
            //StrobeMan.enable();
          }
          if(iCmd == CMD_STOP_STROBE)  {
            // Ensure that we don't prematurely stop a strobe because of multiple
            // triggers being used simultaneously. Only works on last-activated program.
            // *** We send over iParam to indicate what program we are addressing.
            //if(StrobeMan.iCurProg == iParam) {StrobeMan.stopStrobe();}
          }
          
          // For CMD_ALL_ON/OFF commands: iLight = board number --- iParam = VKC_? color
          if(iCmd == CMD_ALL_ON)     {_lman->setAllLamps(LS_ON,iParam);}
          if(iCmd == CMD_ALL_OFF)    {_lman->setAllLamps(LS_OFF,0);}
          if(iCmd == CMD_FLOOD_ON)   {_lman->setFlood(iLight,LS_ON,iParam);}     // Flood on board-x of color iParam
          if(iCmd == CMD_FLOOD_OFF)  {_lman->setFlood(iLight,LS_OFF,VKC_NONE);}  // Turn off entire board
        }
      }
  };
#endif
